import { genKey, isArr, isFunc, isObj } from "./tools"
import { KeyValues } from './types'

type TreeConfig = {
  key?: string,
  children?: string
}

type GetParentFunctionArgs<T> = {
  data: T[],
  config?: TreeConfig,
  node?: T | string | number | ((value: T) => boolean)
}

type FindFuncArgs<T> = {
  data: T[],
  config?: TreeConfig,
  callback: (node: T) => boolean
}

type JsonResultConvertedTreeNode = {
  _k: number,
  type: string,
  label: string,
  value: any,
  children?: JsonResultConvertedTreeNode[]
}

const treeUtil = {
  findParent <T>(options: GetParentFunctionArgs<T>): T {
    const { data, node, config } = options
    const { key: idKey = 'key', children = 'children' } = config || {}

    const isIn = isFunc(node)
      ? (arr: T[]) => arr.some(node as any)
      : isObj(node)
        ? (arr: T[]) => arr.some(_ => _ === node)
        : (arr: T[]) => arr.some(_ => _[idKey] === node)

    return (function find (data: T[]) {
      const waitChecked = []

      for (let i = 0, len = data.length; i < len; i++) {
        const item = data[i]
        const childs: T[] = item[children] || []
        if (isIn(childs)) return item
        waitChecked.push(...childs)
      }

      return waitChecked.length ? find(waitChecked) : null
    })(data)
  },
  find <T>(options: FindFuncArgs<T>) {
    const { data, config, callback } = options
    const { children = 'children' } = config || {}

    return (function fn (data: T[]) {
      const waitChecked = []

      for (let i = 0, len = data.length; i < len; i++) {
        const item = data[i]
        const childs: T[] = item[children] || []
        if (callback(item)) {
          return item
        }
        waitChecked.push(...childs)
      }

      return waitChecked.length ? fn(waitChecked) : null
    })(data)
  },
  jsonResultToTree (obj: KeyValues) {
    const result: JsonResultConvertedTreeNode[] = []

    ;(function fn (obj, parNode?: typeof result[0]) {
      const keys = Object.keys(obj)
      keys.forEach(k => {
        const v = obj[k]
        const item: typeof result[0] = {
          _k: genKey(),
          label: k,
          value: k,
          type: isArr(v) ? 'array' : typeof v
        }
        if (parNode) {
          parNode.children = [...(parNode.children || []), item]
        } else {
          result.push(item)
        }

        if (isArr(v)) {
          const v1 = v[0]
          if (v1 && isObj(v1)) {
            fn(v1, item)
          }
        } else if (v && isObj(v)) {
          fn(v, item)
        }
      })
    })(obj)

    return result
  },
  findRelatedNodes <T>(options: FindFuncArgs<T>) {
    const result: T[] = []

    const node = treeUtil.find(options)
    if (node) {
      result.push(node)
      ;(function fn (node) {
        const parNode = treeUtil.findParent({ ...options, node })
        if (parNode) {
          result.push(parNode)
          fn(parNode)
        }
      })(node)
    }

    return result.reverse()
  }
}

export default treeUtil
